﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace IndexedList
{
    public abstract class BaseListIndex<T> : IListIndex<T> where T : new()
    {
        static System.Reflection.MethodInfo concatMethodInfo = typeof(BaseListIndex<T>).GetMethod("GetHashFromValues", BindingFlags.NonPublic | BindingFlags.Instance);// new Type[] { typeof(IList<object>) });
        protected Func<T, int> ItemHashFunction;
        Dictionary<string, PropertyInfo> indexfields = new Dictionary<string, PropertyInfo>();
        Dictionary<string, int> indexOrders = new Dictionary<string, int>();
        string name;

        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                    name = GetDefaultIndexName();
                else
                    name = value;
            }
        }
        public Dictionary<string, PropertyInfo> IndexFields
        {
            get
            {
                return indexfields;
            }
            private set
            {
                indexfields = value;
                ItemHashFunction = GenerateItemHashFunction();

                int i = 0;
                foreach (var item in indexfields.Keys)
                {
                    indexOrders.Add(item, i++);
                }
            }
        }

        public BaseListIndex()
        {
            this.Name = GetDefaultIndexName();
        }

        public BaseListIndex(ISet<string> fieldNames)
            : this("", fieldNames)
        {
            this.Name = GetDefaultIndexName();
        }

        public BaseListIndex(Expression<Func<T, object>> expression)
            : this("", expression)
        {
            this.Name = GetDefaultIndexName();
        }

        public BaseListIndex(string name, Expression<Func<T, object>> expression)
        {
            SetIndexFieldsByExpression(expression);
            this.Name = name;
        }

        public BaseListIndex(string name, ISet<string> fieldNames)
        {
            this.IndexFields = new Dictionary<string, PropertyInfo>();
            foreach (var fieldName in fieldNames)
            {
                this.IndexFields.Add(fieldName, typeof(T).GetProperty(fieldName));
            }
            this.Name = name;
        }

        string GetDefaultIndexName()
        {
            return string.Join("_", IndexFields.Keys.ToArray());
        }

        public void SetIndexFieldsByExpression(Expression<Func<T, object>> keySelector)
        {
            this.IndexFields = FieldInfoResolver<T>.GetFieldNames<T>(keySelector);
        }

        public abstract void IndexItem(T data);
        public abstract void ClearIndexData();
        public abstract void RemoveIndex(T item);

        protected abstract IEnumerable<T> FindItems(int key);
        protected abstract T FindSingleItem(int key);

        public void IndexList(IEnumerable<T> indexedList)
        {
            foreach (T item in indexedList)
            {
                this.IndexItem(item);
            }
        }

        public IEnumerable<T> FindItems(IList<object> values)
        {
            int key = GetHashFromValues(values);
            return FindItems(key);
        }

        public T FindSingleItem(IList<object> values)
        {
            int key = GetHashFromValues(values);
            return FindSingleItem(key);
        }

        public IEnumerable<T> FindItems(T item)
        {
            int key = ItemHashFunction(item);
            return FindItems(key);
        }
        
        public T FindSingleItem(T item)
        {
            int key = ItemHashFunction(item);
            return FindSingleItem(key);
        }
        
        public IEnumerable<T> FindItems(Dictionary<string, object> fieldValues)
        {
            object[] sortedvalues = SortValues(fieldValues);

            int key = GetHashFromValues(sortedvalues);
            return FindItems(key);
        }
        
        public T FindSingleItem(Dictionary<string, object> fieldValues)
        {
            object[] sortedvalues = SortValues(fieldValues);

            int key = GetHashFromValues(sortedvalues);
            return FindSingleItem(key);
        }

        object[] SortValues(Dictionary<string, object> fieldValues)
        {
            object[] sortedvalues = new object[fieldValues.Count];
            foreach (var indexorder in this.indexOrders)
            {
                sortedvalues[indexorder.Value] = fieldValues[indexorder.Key];
            }
            return sortedvalues;
        }
        protected virtual Func<T, int> GenerateItemHashFunction()
        {
            ParameterExpression result = Expression.Parameter(typeof(int), "result");
            ParameterExpression inputObject = Expression.Parameter(typeof(T), "input");

            Expression expressionToHash;
            Expression[] concatParams = new Expression[this.IndexFields.Count()];

            int j = 0;
            foreach (var fieldName in this.IndexFields)
            {
                MemberExpression parameter = Expression.Property(inputObject, fieldName.Key);
                concatParams[j++] = Expression.Convert(parameter, typeof(object));
            }

            Expression paramArray = Expression.NewArrayInit(typeof(object), concatParams);
            expressionToHash = Expression.Call(Expression.Constant(this), concatMethodInfo, paramArray);

            Expression finalExpression = Expression.Assign(result, expressionToHash);

            Expression<Func<T, int>> indexKeyFunction = Expression.Lambda<Func<T, int>>(
               Expression.Block(new[] { result },
                finalExpression), inputObject);

            return indexKeyFunction.Compile();

        }
        protected virtual int GetHashFromValues(IList<object> values)
        {
            string res = "";
            if (values.Count == 1)
            {
                return (values[0] ?? "").GetHashCode();
            }

            foreach (var item in values)
            {
                res += (item ?? "").GetHashCode().ToString() + "_";
            }
            return res.GetHashCode();
        }


    }
}
